package pub.cleangao.mybatis.structure;


import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import pub.cleangao.common.util.ReflectUtil;
import pub.cleangao.mybatis.annotation.Dialect;
import pub.cleangao.mybatis.meta.TableDefinition;
import pub.cleangao.mybatis.runtime.MybatisContext;

import java.nio.charset.StandardCharsets;
import java.util.*;


/**
 * mybatis-xml-reload核心xml热加载逻辑
 */
@Slf4j
public record MybatisXmlReload(List<SqlSessionFactory> sqlSessionFactories) {



    /**
     * 载入配置文件
     */
    public void xmlReload() {
        Map<String, Dialect> mappers = MybatisContext.getMapperXmlDialectMap();
        if (mappers == null || mappers.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Dialect> entry : mappers.entrySet()) {

            Dialect dialect = entry.getValue();
            // 1. 加载通用模板
            loadMapperFile(new ClassPathResource("mapper-template/" + dialect.name() + "TargetSource.xml"));
            // 2. 加载每个MapperInterface对应的模板
            String filePath = entry.getKey();
            Resource mapperLocation = new ByteArrayResource(MybatisContext.getMapperXmlContent(filePath).getBytes(StandardCharsets.UTF_8));
            loadMapperFile(mapperLocation);
        }
    }

    private void loadMapperFile(Resource mapperLocation) {
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactories) {
            try {
                Configuration targetConfiguration = sqlSessionFactory.getConfiguration();
                // 启用二级缓存
                targetConfiguration.setCacheEnabled(true);
                Map<String, ResultMap> resultMaps = (Map<String, ResultMap>) ReflectUtil.getFieldValue(targetConfiguration, "resultMaps");
                Map<String, XNode> sqlFragmentsMaps = (Map<String, XNode>) ReflectUtil.getFieldValue(targetConfiguration, "sqlFragments");
                Map<String, MappedStatement> mappedStatementMaps = (Map<String, MappedStatement>) ReflectUtil.getFieldValue(targetConfiguration, "mappedStatements");
                XPathParser parser = new XPathParser(mapperLocation.getInputStream(), true, targetConfiguration.getVariables(), new XMLMapperEntityResolver());
                XNode mapperXNode = parser.evalNode("/mapper");
                List<XNode> resultMapNodes = mapperXNode.evalNodes("/mapper/resultMap");
                // 1.避免与自定义的mapper冲突，删除全部（namespace）节点
                String namespace = mapperXNode.getStringAttribute("namespace");
                for (XNode xNode : resultMapNodes) {
                    String id = xNode.getStringAttribute("id", xNode.getValueBasedIdentifier());
                    resultMaps.remove(namespace + "." + id);
                }
                // 2.避免与自定义的mapper冲突，删除全部（sql）节点
                List<XNode> sqlNodes = mapperXNode.evalNodes("/mapper/sql");
                for (XNode sqlNode : sqlNodes) {
                    String id = sqlNode.getStringAttribute("id", sqlNode.getValueBasedIdentifier());
                    sqlFragmentsMaps.remove(namespace + "." + id);
                }
                // 3.避免与自定义的mapper冲突，删除全部（method）节点
                List<XNode> msNodes = mapperXNode.evalNodes("select|insert|update|delete");
                for (XNode msNode : msNodes) {
                    String id = msNode.getStringAttribute("id", msNode.getValueBasedIdentifier());
                    mappedStatementMaps.remove(namespace + "." + id);
                }
                // 4.删除自定义节点后，执行合并；将增强的方法合并至自定义节点
                try {
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }


}
